3W - EFS 드라이버, 인스턴스 스토어 활용
개요
이전 글에서는 스토리지 성능 측정 도구인 kubestr과, EBS 드라이버를 활용해 볼륨 스냅샷 등의 작업을 수행해보았다.
이번 글에서는 EFS CSI 드라이버, 그리고 인스턴스 스토어를 활용하며, 간단하게 특징 차이를 짚고 성능 측정을 해볼 것이다.
아울러 자체적으로 효과적으로 인스턴스 스토어를 효과적으로 사용하기 위한 방향에 대해 진행한 몇 가지 실험과 측정 결과, 결론을 담는다.
사전 지식
EFS CSI 드라이버
AWS EFS CSI Driver는 aws의 파일 스토리지 서비스인 Amazon Elastic File System를 클러스터 스토리지로서 활용할 수 있도록 도와주는 CSI 드라이버이다.[1]
이전에 사용해본 AWS EBS CSI Driver와는 이 정도의 차이를 가진다.
- 이미 프로비저닝된 EFS가 있는 상태에서 이 자원을 활용한다.
- 다중 읽기쓰기(ReadWriteMany)가 가능하다.
- 여러 노드, 여러 가용영역에서 마운팅하여 활용할 수 있으므로 동시 공유를 해야 하는 스토리지가 필요할 때 유용한다.
- 블록 스토리지 지원이 되지 않는다.
- 액세스 포인트를 통한 사용자 접근 제어가 가능하다.
특징이라고 할 만한 것들은, 사실 EFS가 가지는 특징이라고 봐도 무방하다.
애초에 CSI 드라이버라는 게 해당하는 리소스를 스토리지로 사용할 수 있도록 지원하는 것이니..
EFS
그렇다면 EFS는 무엇인가?
AWS에서 지원하는 NFS 파일시스템 스토리지 서비스.
말 그대로 파일시스템을 마운팅하여 활용하는 방식으로 사용할 수 있다.
기본적으로 확장성과 가용성을 고려하여 설계되었기에, Amazon EBS와는 확실히 다른 특징을 가지고 있다.
- 가용성
- 여러 AZ에서 동시 접근 가능
- 또 여러 인스턴스에서 접근하는 것도 가능하다.
- 확장성
- 용량 제한 없이 요구하는 만큼 크기가 확장된다.
- 처리량, 지연시간 역시 알아서 최적화된다.
- 보안
- IAM 기반의 접근 제어가 가능하다.
- 액세스 포인트를 나누어 여러 마운팅 위치를 분할할 수도 있다.
- 어떤 유저는
/direc
을 마운팅하는 반면 어떤 유저는/
을 통째로 마운팅한다던가.
- 어떤 유저는
연결은 tcp 2049 포트를 통해 이뤄지는데, 관련 에이전트를 이용하면 구체적으로 알 필요는 없다.
(대신 보안 그룹 세팅을 위해서는 필요한 지식이다..!)
인스턴스 스토어
인스턴스 스토어는 실제 해당 인스턴스의 물리적 위치에 존재하는 스토리지 공간을 말한다.
몇 가지의 인스턴스 유형을 제외하고 이를 사용할 수 있는데, 아무래도 네트워크를 타야하는 다른 스토리지보다는 훨씬 속도가 빠를 수밖에 없다.
ami 단에서 미리 세팅을 하거나, 보통 nvme를 사용하는 타입은 포맷이 되지 않은 채로 블록이 붙어있다.[2]
이 저장 공간은 인스턴스와 수명을 같이 하는데, 중지되거나 절전이 되도 데이터를 보장하지 않는다.
그래서 실질적으로 활용할 때는 메모리처럼 빠르면서 휘발성있는 공간으로 생각하는 편이 좋다.
물론 메모리만큼 빠를 순 없다.
인스턴스의 기본 루트 볼륨인 EBS는 사실 시간 당 최대 요청량이 정해져있다.
그래서 이를 초과하는 요청이 발생하면 쓰로틀링이 발생하게 되는데, 이럴 때 인스턴스 스토어를 활용하면 그런 제한에서 상대적으로 자유로워질 수 있다.
containerd
Containerd는 쿠버네티스에서 많이 쓰이는 컨테이너 런타임 중 하나다.
대중적으로 활용되는데, EKS에서도 활용되고 kubeadm도 기본적으로는 이 녀석을 설치해준다.
kubelet은 containerd와 통신을 하며 노드에 컨테이너들을 관리한다.
containerd는 파일시스템 상으로 root, state라는 두 가지 디렉토리를 분리하여 사용한다.
roote는 영구적으로 남는 데이터들을 보관하는 영역이다.
스냅샷, 메타데이터, 이미지 등이 보관되는 장소이다.
플러그인들도 여기에 보관되며 각자 독립적인 디렉토리로 관리된다.
state는 컨테이너가 실행될 때만 생기는 일시적인 데이터들이 보관되는 영역이다.
사진처럼 컨테이너 thin layer가 여기에 보관된다.
또 소켓, PID, 마운트 포인트 등의 정보들이 여기에 담긴다.
version = 2
# persistent data location
root = "/var/lib/containerd"
# runtime state information
state = "/run/containerd"
# set containerd's OOM score
oom_score = -999
...
/etc/containerd/config.toml
에서 관련 설정을 해줄 수 있다.
여기에서 궁금해지는 점은, 어떤 공간에서 disk io가 많이 발생하는가인데, 아래 실습에서 다루겠다.
- 실습 진행
- 어떻게 실습했는지 모든 내용을 정리한다.
- 사진과 더불어 스크립트도 전부 남기도록 한다.
- 실습 편의성과 가독성을 고려해 스크립트는 코드로, 결과는 사진으로 남기는 것으로 명확하게 정한다.
- 결론
- 실습을 통해 알게 된 내용, 혹은 고민 사항이나 판단을 정리한다.
- 추가 고민
- 스터디에서 나온 추가 과제, 심화 고민 사항을 정리한다.
- 내가 고민한 사항에 대한 정리 역시 포함한다.
실습 진행
EFS CSI 드라이버 세팅 및 테스트
본격적으로 efs 드라이버를 실습해보자.
테라폼 세팅
resource "aws_efs_file_system" "efs" {
creation_token = "my-product"
lifecycle_policy {
transition_to_ia = "AFTER_30_DAYS"
}
}
data "aws_iam_policy_document" "efs" {
statement {
sid = "efs-mount"
effect = "Allow"
principals {
type = "AWS"
identifiers = ["*"]
}
actions = [
"elasticfilesystem:ClientMount",
"elasticfilesystem:ClientWrite",
]
resources = [aws_efs_file_system.efs.arn]
}
}
resource "aws_efs_file_system_policy" "efs" {
file_system_id = aws_efs_file_system.efs.id
policy = data.aws_iam_policy_document.efs.json
}
resource "aws_efs_mount_target" "efs" {
count = length(module.eks_vpc.public_subnets_id)
file_system_id = aws_efs_file_system.efs.id
subnet_id = module.eks_vpc.public_subnets_id[count.index]
security_groups = [
data.aws_security_group.cluster.id
]
}
먼저 EFS를 만들어주는 작업을 한다.
기본적으로 AWS 리소스에서 파일시스템에 마운팅과 읽기 작업을 할 수 있도록 정책을 적용하고, EKS 클러스터에서 마운팅할 수 있도록 타겟을 만들어준다.
세팅의 편의성을 위해 별도의 보안그룹을 만들지 않고 클러스터 노드들이 가지는 보안그룹을 설정해주어 바로 마운팅이 가능하도록 만들었다.
프로비저닝이 완료되면, 이렇게 EFS 네트워크 탭에 들어가 이렇게 마운팅 타겟이 설정된 것을 확인할 수 있다.
resource "aws_eks_addon" "efs_csi" {
cluster_name = module.eks.cluster_name
addon_name = "aws-efs-csi-driver"
addon_version = "v2.1.4-eksbuild.1"
resolve_conflicts_on_update = "PRESERVE"
configuration_values = jsonencode({ })
service_account_role_arn = module.efs_csi_irsa.iam_role_arn
}
module "efs_csi_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "5.52.2"
role_name = "efs-csi"
attach_efs_csi_policy = true
force_detach_policies = true
oidc_providers = {
eks = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:efs-csi-controller-sa"]
}
}
}
다음으로 클러스터가 구축된 이후 바로 애드온이 설치되도록 IRSA와 애드온 세팅을 해주었다.
애드온 설정에서 기본 스토리지 클래스를 만드는 옵션이 없어서 별도의 세팅을 넣지는 않았다.
구축 확인 및 세팅
aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output text
NFS 서버 스토리지 클래스 세팅할 때 주소를 넣어주듯이, EFS 볼륨의 ID를 먼저 알아내야 한다.
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: efs-sc
provisioner: efs.csi.aws.com
parameters:
provisioningMode: efs-ap # 엑세스엔드포인트 모드. 다른 것 불가능
fileSystemId: fs-92107410
directoryPerms: "700"
basePath: "/dynamic_provisioning" # 해당 볼륨의 어디를 활용할 건지
subPathPattern: "${.PVC.namespace}/${.PVC.name}" # optional
ensureUniqueDirectory: "true" # 알아서 디렉 이름이 고유하도록 uuid 붙이는 설정
reuseAccessPoint: "false" # optional
동적 프로비저닝을 위해 이렇게 스토리지 클래스를 만들었다.
filesystemId
에 위에서 알아낸 파일시스템 id를 기입해주면 된다.
구축 확인 및 성능 테스트
kubestr을 통해 EFS의 스토리지 성능을 확인해보자!
read의 경우 iops 18435, bw 73741.
부하를 너무 심하게 주었는지 이전과 같은 양식으로 num jobs 16으로 진행하니 계속 context deadline exceeded 오류가 나와 값을 8로 낮춰주었다.
(ebs 테스트 사진도 이 설정으로 전부 교체했다.)
write의 경우에는 iops 6000, bw 24000 정도.
EBS에서는 iops가 3000, bw 12900 정도로 고정돼있던 결과와 사뭇 다르다.
EFS의 경우에는 직접적으로 iops, bw값을 설정할 수가 없으며 사용량에 따라 알아서 유동적으로 조정이 된다.
최소한 기본 EBS값보다는 높게 나오는 것이 확인된다.
여담이지만, 테스트를 하자마자 바로 처리량 alert가..
다중 노드 동시 접근
EBS 드라이버는 ReadWriteOnce
로서 한 번에 한 노드, 심지어 한 번에 한 파드만이 마운팅을 할 수 있었다.
그렇다면 다중 접속이 특기인 EFS는 어떨까?
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: efs-claim
spec:
accessModes:
- ReadWriteOnce
storageClassName: efs-sc
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: efs-app
name: efs-app
spec:
replicas: 3
selector:
matchLabels:
app: efs-app
template:
metadata:
labels:
app: efs-app
name: efs-app
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: "kubernetes.io/hostname"
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: efs-app
containers:
- name: app
image: centos
command: ["/bin/sh"]
args: ["-c", "while true; do echo $(hostname ; hostname -I) >> /data/out; sleep 5; done"]
volumeMounts:
- name: persistent-storage
mountPath: /data
volumes:
- name: persistent-storage
persistentVolumeClaim:
claimName: efs-claim
세팅은 이런 식으로 진행했다.
워크로드는 토폴로지 스프레드를 걸어 확실하게 모든 파드가 각 노드에 스케줄링되도록 유도했다.
그리고 각 파드는 자신의 이름과 ip 주소를 출력하여 같은 공간에 입력한다.
언뜻 보면 이상할 수도 있는 부분으로, PVC에서 ReadWriteOnce
를 모드로 명시하고 있다.
Once는 한번에 한 노드에서만 접근이 가능하다는 것을 나타내는 것이라 잘못된 설정이 아닌가 생각할 수 있겠다.
그러나 어디까지나 액세스 모드는 PV와 PVC를 매칭하기 위한 조건으로 사용되는 필드라는 것을 명심해야 한다.
실제로 해당 필드는 접근 방식에 대한 어떠한 제한도 걸지 않으며, 이에 대한 구현과 적용은 전적으로 CSI 드라이버에서 담당할 뿐이다.
NFS와 마찬가지로 EFS는 파일시스템 볼륨으로서 여러 공간에서의 동시 접속을 지원하기 때문에 실상 ReadWriteOnce
로 접근 모드 필드를 작성하더라도 아무런 영향이 없다.
k exec -ti efs-app-6f95dfc768-p5d29 -- cat /data/out
적당한 파드 하나 잡아서 출력을 걸어보면, 각 노드에 위치한 컨테이너들이 열심히 일을 하고 있는 것을 볼 수 있다.
문제 없이 다중 노드 접속, ReadWriteMany가 적용되고 있는 것이다.
콘솔에서 확인해보면 클레임 하나에 한 액세스포인트가 만들어진 것이 보인다.
인스턴스 스토어 활용
이번에는 인스턴스 스토어를 활용해서 조금 더 빠른 스토리지를 활용하는 방법을 보도록 한다.
인스턴스 스토어 기본 세팅 및 확인
인스턴스 스토어는 모든 인스턴스 타입이 가지고 있는 것은 아니다.
aws ec2 describe-instance-types \
--filters "Name=instance-type,Values=c*" "Name=instance-storage-supported,Values=true" \
--query "InstanceTypes[].[InstanceType, InstanceStorageInfo.TotalSizeInGB]" \
--output table
이렇게 각 인스턴스 타입이 가진 인스턴스 스토어의 스펙을 확인할 수 있는데, T 시리즈에는 인스턴스 스토어가 없다는 것을 알 수 있다.
c 시리즈 중 가장 저려미인 c5d.large를 활용하여 간단하게 인스턴스를 만들어보자.
이대로라면 50기가의 디바이스를 받게 될 것이다.
콘솔에서 EC2를 만들 때도 인스턴스 스토어에 대한 간략한 정보를 확인할 수 있다.
현재 노드에 붙은 nvme 모델은 이렇게 확인할 수 있다.
이 부분의 이유는 잘 모르겠는데, 막상 들어가서 확인해보면 디바이스 이름은 nvme1n1이었다.
찾아보니 콘솔에서 출력되는 디바이스 이름과 인스턴스에서 보이는 값은 항상 다를 수 있다고 한다.
아무튼 현재는 블록 형태로 연결만 돼있는 상태로, 파일시스템을 포맷해서 마운팅을 하는 작업을 해야 한다.
기본 루트 파일시스템은 xfs로 포맷팅되어 있고, 같은 방식으로 포맷을 하도록 한다.
mkfs -t xfs /dev/nvme1n1
mkdir /data
mount /dev/nvme1n1 /data
아무튼 성공적으로 인스턴스 스토어 저장소를 활용할 준비가 끝났다.
이제 이런 식으로 인스턴스 스토어를 활용하는 노드 그룹을 만들면 되겠다.
탐구 - 인스턴스 스토어는 어디에 쓰는 게 좋은가?
이 공간을 사용하는 방법에 대해서는 자료가 어느 정도는 있는 편이다.[3]
그런데 이 공간을 무엇으로 활용하는 관리자의 선택이다.
두 가지 정도의 활용 방안이 있을 것 같다.
- 로컬 볼륨 공간
- 메모리 만큼은 당연히 아니겠지만, 그래도 그나마 빠른 속도의 공간이 필요하다면 요긴할 것이다.
- 이렇게 사용한다면, 정적 프로비저닝을 통해 관리하는 게 좋을 것 같다.
- 용도가 명확한 경우에만 사용할 수 있도록 제한하면서, 사용할 수 있는 양도 명확하게 지정을 하는 방식으로 말이다.
- 컨테이너 실행 공간.
- 위에서 말했듯이 containerd는 노드의 공간을 root와 state로 분리해 사용한다.
그렇다면 여기에서 추가 의문이 생긴다.
containerd를 위한 공간으로 인스턴스 스토어를 쓴다면, 어떤 공간을 할당하는 것이 좋을 것인가?
(사실 파티션을 나누던가, 그냥 bind mount로 둘 다 쓰는 것도 가능할 것 같긴 하다.)
탐구 - containerd는 어디에서 disk io가 많이 일어나는가?
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: instance-test
name: instance-test
spec:
replicas: 3
selector:
matchLabels:
app: instance-test
template:
metadata:
labels:
app: instance-test
spec:
nodeSelector:
disk: instance_store
containers:
- image: centos
name: con1
command: ["/bin/sh"]
args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; done"]
- image: centos
name: con2
command: ["/bin/sh"]
args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; done"]
간단하게 노드 셀렉터로 컨테이너 루트 디렉토리에 쓰기 작업을 하는 디플로이먼트를 만들었다.
처음에는 3개만 두지만, 이어서 25개, 50개 정도로 늘리는 테스트까지 겸할 것이다.
구체적으로 실험을 하는 방법은 다음과 같다.
state 영역에 인스턴스 스토어를 배치한다.
root 영역은 별다른 세팅을 하지 않고, root ebs 볼륨을 그대로 사용하게 될 것이다.
이때 각 디스크의 지표를 통해 disk io를 비교할 것이다.
그리고 이때 워크로드를 실행함으로써 생기는 변화를 확인한다.
생각해보니 말이 좀 이상한 게, 차라리 인스턴스 스토어에 state 영역을 배치한다는 표현이 더 정확한 것 같기도..
당장 더 나은 표현이 생각나지 않아 그냥 이렇게 남겨둔다.
단순 디렉토리 사용량 확인
watch -c -n 0.1 du -sh $(pwd)
root 영역인 /var/lib/containerd
, state 영역인 /run/containerd
에 대해 용량을 모니터링해본다.
왼쪽이 state, 오른쪽이 root이다.
간단한 워크로드를 띄우고 스케일을 계속 높였다.
처음에 이미지를 받는 순간에 root의 크기도 증가했으나, 이후에 계속 스케일 아웃을 할 때마다 state 부분이 크게 올랐다.
어찌보면 당연하다고 할 수 있겠다.
확실하게 알고 말하는 것은 아니다만, 파일 구조를 보니 root에서 캐시된 이미지나 각종 정보들을 컨테이너를 실행할 때 state에 상당 부분 옮긴 채로 실행하는 것으로 보였다.
워크로드를 지우면 state 부분은 완벽하게 원래 상태로 돌아간다.
컨테이너를 실행하며 생기는 부산물들이니 사라지는 것은 당연한 수순이긴 하다.
현 상황은 디스크에 차지하는 용량을 보는 것이기 때문에 정확히 의문에 대한 답을 제공할 수 없다.
다만, 컨테이너가 실행된 이후 생긴 부산물에 대해 이뤄지는 작업들은 결국 state의 영역에서 일어난다고 변화를 보며 유추할 수 있다.
참고로 state 영역에 runtime이라는 디렉토리에 실제 컨테이너의 thin 레이어는 이렇게 확인할 수 있다.
프로메테우스 스택을 통한 disk io 확인
관측 가능성 도구인 프로메테우스-그라파나를 이용해 조금 더 제대로 모니터링해보자.
kubectl create namespace monitoring
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install prometheus prometheus-community/kube-prometheus-stack --namespace monitoring
다른 글을 참고해 쉽게 설치했다.[4]
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
type=LoadBalancer
여기에 나는 추가적으로 그라파나를 로드밸런서로 만드는 작업을 겸했다.
기본 관리자 아이디는 admin, 패스워드는 prom-operator이다.
대시보드 중 node 정보를 보는 대시보드에 들어가면 이렇게 io time, 쓰기량과 읽기량을 확인할 수 있다.
사실 나는 state 영역에서 io가 더 많이 일어날 거라 생각하고 진행한 실험인데, 간단한 모니터링 결과는 많이 달랐다.
state 영역의 지표는 거의 보이지도 않는다.
치솟은 작업은 root 영역에 대한 쓰기 작업으로, 처음 워크로드를 실행할 때 매우 높게 지표가 치솟고 이후 스케일아웃을 할 때 또 잠시 오른다.
중간에 살짝 올라온 주황색이 state 영역에 일어나는 쓰기 작업인데, 유의미하게 많이 크게 측정되지는 않는다.
오랫동안 파드를 방치해뒀을 때는 두 값 모두 변동 없이 평이한 상태로 유지됐다.
이건 리소스를 지울 때 상황인데, 이때도 root 영역에서 더 많은 disk io가 관측됐다.
탐구 결론
이로부터 내가 생각한 바는 이렇다.
disk io에 가장 큰 영향을 주는 것은 컨테이너가 생성, 삭제되는 시점이다.
이때 이미지를 받아서 로컬에 저장하고, 이미지를 읽이서 컨테이너 프로세스를 실행하는 작업으로 인해 큰 io가 발생하게 된다.
그리고 이 동작들은 실행 중일 때의 데이터를 담는 state 영역보다는 root 영역에서 더 많이 일어난다.
state 영역에 thin layer를 만들고, overlay 설정을 하는 순간에는 state에서도 io가 발생하지만, 대체로 root 보다 크지는 않다.
그리고 이건 일부러 컨테이너 내 루트 파일시스템에 쓰기 작업이 일어나도록 유도한 결과인데, 실제 어플리케이션들의 경우에는 외부 스토리지를 사용하거나, 아니면 스토리지 공간을 많이 활용하지 않는다.
그렇게 봤을 때, state 영역에 대한 io 작업은 훨씬 덜 일어날 것이라고 생각해볼 수 있다.
pid, 각종 stdout이 일어난다고 해도 해당 값들이 이 차이를 메꿀 정도로 유의미하게 일어날지는 모르겠다.
다만 state에서 작업이 일어난다고 한다면, 실행 중인 시점에 결과를 내뿜을 것이라고 생각한다.
위의 결과에서는 컨테이너가 실행 중인 시점에는 별 값의 변동이 없었으나, 어플리케이션에 따라서는 그런 시점에 state 영역에 변화가 발생할 가능성도 무시할 수 없다고 본다.
이로부터 나는 이런 결론을 내리고자 한다.
인스턴스 스토어를 containerd에 활용할 때, 적합한 로컬 공간은 root 영역이다.
이 부분에서의 disk io를 최적화한다면 컨테이너 생성 속도도 빨라지며 한편으로 EBS 요청량 제한에서 자유로워지는데 도움이 될 것이다.
반대로 세팅 후 검증
이 결론을 확실시하기 위해, 이번에는 root 영역을 인스턴스 스토어에 배치하여 동일한 실험을 진행한다.
1.31 버전에서 containerd의 공간을 마운팅하면 노드에 캐시된 pause 이미지가 사라져 버려 노드 세팅이 정상적으로 이뤄지지 않는다.
eks에서는 pause 이미지를 외부 저장소에서 가져오지 않고 미리 사전에 세팅해두는데, 이때문에 함부로 mounting을 했다가는 에러가 발생한다.
안그래도 따끈하게 비슷한 이슈를 겪은 케이스가 있고, 마운팅 전의 데이터들을 그대로 마운팅한 공간에 넣으라고 안내한다.[5]
mkdir -p /cache
cp -r /var/lib/containerd /cache
rm -rf /var/lib/containerd/*
mkdir -p /var/lib/containerd
mount /dev/nvme1n1 /var/lib/containerd
cp -r /cache/containerd /var/lib
초기 데이터에 이렇게 잠시 containerd의 내부 파일들을 꺼내온 후 다시 넣어주는 로직을 추가해주면 된다.
간단하게 iostat으로 확인해보면, 쓰기 작업이 상당히 많이 일어나는 것을 확인할 수 있다.
이미 이걸로만 확인해도 root 영역의 쓰기가 더 많을 것이라는 것을 나이브하게 유추할 수 있다.
동일하게 실험을 진행했다.
처음 이미지를 받아오는 과정에서 순간 많은 쓰기가 발생했고, 이후 스케일업을 할 때는 그나마 root와 state가 비슷한 정도의 쓰기 처리량을 보였다.
별도의 설정을 하진 않은 것 같은데, 컨테이너가 실행되는 중에도 root 영역에 대한 쓰기가 꽤 많이 일어나고 있다는 것이 조금 의외였다.
디스크 사용량에서도, 결국 root 영역에서는 유의미한 차이가 발생했다.
이 상황은 root 영역만 마운팅을 했기 때문에 사실 state 영역에 대한 지표는 노드 전체에서 일어나고 있는 디스크 io 양이 합쳐진 결과라는 것을 감안해야 한다.
그런 점에서 보았을 때 정말 어마무시한 차이가 난다고 생각해볼 수 있다.
그러니까, 디스크 io 제한을 이유로 인스턴스 스토어를 사용한다면 무조건 /var/lib/containerd
를 마운팅하는 것이 정답이라고 이제 확정짓겠다.
추가 - 로컬 스토리지로서의 인스턴스 스토어
인스턴스 스토어를 활용하는 방법으로 containerd만이 있는 건 아니다.
위에서 언급했듯이 로컬 스토리지로서의 활용하는 것이다.
이 경우엔 파드와 라이프사이클을 같이 하거나, 영속성이 필요한 데이터의 경우 확실하게 고가용성 및 백업 전략이 마련된 상태에서 활용하는 것이 좋을 것이다.
관련한 간단한 지표도 확인해보자.
나는 한 노드에서 계속 실험을 진행했기에, 언마운팅 후 로컬 스토리지로 쓰기 위한 세팅을 다시 해주는 식으로 실습을 진행했다.
systemctl stop kubelet
systemctl stop containerd
cp -r /var/lib/containerd /cache/containerd
umount /var/lib/containerd
cp -r /cache/containerd/ /var/lib/containerd
systemctl restart containerd
systemctl restart kubelet
일단 기존에 사용하던 인스턴스 스토어를 재활용하고자 해당 노드에 들어가서 원복시킨다.
[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.k8s.io/pause:3.9"
정확한 이유는 모르겠으나, 단순히 이렇게 하는 것만으로는 pause 이미지가 제대로 남지 않는 듯했다.
/etc/containerd/config.toml
에서 원격에서 pause 이미지를 가져오도록 수정해주었다.
systemctl restart containerd
systemctl restart kubelet
이제 이 친구를 다시 포맷팅해서 로컬 스토리지로 활용하면 될 것이다.
kubestr을 통해 테스트를 할 것이기에 로컬 프로비저너가 활용할 공간에 마운팅해주고, 내부를 싹 지워줄 것이다.
k -n local-path-storage get cm local-path-config -o yaml
로컬 프로비저너가 프로비저닝에 활용하는 공간을 확인한다.
opt에 있다는 것을 알 수 있으니, 해당 공간에 다시 인스턴스 스토어를 마운팅하자.
mount /dev/nvme1n1 /opt/local-path-provisioner
rm -rf /opt/local-path-provisioner/*
그 다음 싹 날려준다..ㅋ
이제 테스트를 진행해본다!
k -n local-path-storage rollout restart deployment local-path-provisioner
kubestr fio -f read.fio -s local-path --size 10G --nodeselector disk=instance_store
kubestr fio -f write.fio -s local-path --size 10G --nodeselector disk=instance_store
참고로 이미 프로비저너가 설치돼있었다면 덮어씌워진 스토리지를 인식하지 못할 가능성이 있다.
mount propagation 설정을 안 해뒀는지 재시작 없이는 제대로 동작하지 않는 듯하다.
간단하게 인스턴스 스토어 스토리지 성능 측정을 해보면.. 왜인지, 성능 측정에 계속 실패한다.
aws 문서에는 읽기와 쓰기에 대해 이정도를 지원한다고 한다.
솔직히 실패에 대한 정확한 이유는 모르겠다(아는 분이 알려주셨으면 좋겠다).
(인스턴스 스토어가 더 부하에 민감한 이유는 무엇일까...)
다만 병렬 횟수를 조금 줄여서 다시 시도해서 값을 확인해보았다.
지표 상으로는 문서에 나온 정도의 성능이 확실히 나와주는 게 보인다.
EBS를 스토리지로 활용하는 것보다는 훨씬 성능 상에서 이점을 가진다는 것은 명백히 드러난다.
번외 - cloud init을 이용한 amazon 2023 테라폼 세팅
cloudinit_pre_nodeadm = [
{
content_type = "multipart/mixed; boundary=\"BOUNDARY\""
content = <<-EOT
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="BOUNDARY"
--BOUNDARY
Content-Type: text/cloud-config
#cloud-config
ssh_authorized_keys:
- ${aws_key_pair.eks_key_pair.public_key}
--BOUNDARY
Content-Type: text/x-shellscript
#!/bin/bash
yum install nvme-cli links tree tcpdump sysstat ipvsadm ipset bind-utils htop -y
mkfs -t xfs /dev/nvme1n1
systemctl stop containerd
rm -rf /run/containerd/*
mkdir -p /run/containerd
mount /dev/nvme1n1 /run/containerd
systemctl start containerd
echo "/dev/nvme1n1 /run/containerd xfs defaults,noatime 0 2" >> /etc/fstab
--BOUNDARY--
EOT
},
참고로 아마존 리눅스 2023은 eks 노드 세팅이 일어날 때 nodeadm이라는 툴을 활용하며, 관련한 추가 세팅은 Cloud-init의 양식에 맞춰 세팅을 해줘야 한다.
이것 때문에 꽤나 까다로운 이슈가 생기는데, remote access(ssh) 접근 세팅과 커스텀 런치 템플릿 허용 세팅 간에 충돌이 일어난다.
그래서 디스크 마운팅 세팅을 하는 동시에 ssh 설정을 하려면 무조건 세팅을 고도화시켜야 한다..
주제를 많이 벗어나 간단하게만 보이도록 한다.
cloud init의 경우 user data로 들어간 설정을 이런 식으로 읽어 낸다.
MIME 타입으로 데이터를 넣되, content-type에 내가 넣고자 하는 값이 어떤 타입인지 명시를 해주면 이를 읽고 cloud init이 반영한다.
하도 많은 자료를 찾다보니 전부 기억나진 않고 가장 핵심적으로 본 문서는 이거다.[6]
또한 여기에서 어떤 식으로 써야 데이터가 제대로 들어가는지 미리 검증을 할 수 있었다.[7]
cloud init 실행의 로그는 /var/lib/cloud-init.log
이런 식으로 찍힌다.
신기한 것이, cloud config로 세팅하지 않고 쉘 스크립트로 설치한 것들도 전부 제대로 기록된다.
/etc/cloud
에 들어가보면 어떻게 설치가 이뤄질지에 대한 설정 파일이 존재한다.
각 단계에서 실행될 모듈에 대한 정보가 담기는데, scripts_user 부분에서 내가 넣은 설정이 들어가게 된다.
이걸 콘솔에서도 확인해볼 수 있다.
관련한 런치 템플릿은 두 개가 만들어지는데, 하나는 내 데이터가 들어간 녀석이다.
이를 소스로 하여 nodeadm 설정이 들어간 템플릿이 하나 더 만들어진다.
아쉬운 점은, 콘솔에서 실행 시점의 키페어에 대한 표시를 할 수 없다는 것이다.
내가 더 잘 설정하면 가능할지도 모르겠으나 이건 너무 바운더리를 벗어난다.
참고로 nodeadm을 추가로 만져야 한다면 여길 참고하면 되겠다.[8]
{
content_type = "application/node.eks.aws"
content = <<-EOT
---
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
instance:
localStorage:
strategy: RAID0
EOT
}
aws 측에서는 아예 컨텐츠 타입으로 node 관련 양식을 정의해뒀기에 익숙해지면 간편히 설정을 넣을 수 있다.
결론
EFS는 다중 접속, 실시간 공유가 필요한 상황에서 선택하기에 적합한 스토리지이다.
내가 알기로 가격은 EBS 보다 조금 더 비싼데, 그렇기에 필요한 상황에 쓰는 것이 적합하겠다.
인스턴스 스토어를 스토리지로서 활용한다면 가급적 영속성을 다른 수단으로 보장할 수 있을 때 활용하는 것이 좋을 것이다.
아니면 disk io가 많이 발생하는 containerd의 root 영역을 커버하는 것도 성능 최적화에 큰 도움이 되어줄 것이다.
cloud init은, 나처럼 자동화하고 싶어 미친 사람 아니면 굳이 건드릴 필요 없다.
이전 글, 다음 글
다른 글 보기
이름 | index | noteType | created |
---|---|---|---|
1W - EKS 설치 및 액세스 엔드포인트 변경 실습 | 1 | published | 2025-02-03 |
2W - 테라폼으로 환경 구성 및 VPC 연결 | 2 | published | 2025-02-11 |
2W - EKS VPC CNI 분석 | 3 | published | 2025-02-11 |
2W - ALB Controller, External DNS | 4 | published | 2025-02-15 |
3W - kubestr과 EBS CSI 드라이버 | 5 | published | 2025-02-21 |
3W - EFS 드라이버, 인스턴스 스토어 활용 | 6 | published | 2025-02-22 |
4W - 번외 AL2023 노드 초기화 커스텀 | 7 | published | 2025-02-25 |
4W - EKS 모니터링과 관측 가능성 | 8 | published | 2025-02-28 |
4W - 프로메테우스 스택을 통한 EKS 모니터링 | 9 | published | 2025-02-28 |
5W - HPA, KEDA를 활용한 파드 오토스케일링 | 10 | published | 2025-03-07 |
5W - Karpenter를 활용한 클러스터 오토스케일링 | 11 | published | 2025-03-07 |
6W - PKI 구조, CSR 리소스를 통한 api 서버 조회 | 12 | published | 2025-03-15 |
6W - api 구조와 보안 1 - 인증 | 13 | published | 2025-03-15 |
6W - api 보안 2 - 인가, 어드미션 제어 | 14 | published | 2025-03-16 |
6W - EKS 파드에서 AWS 리소스 접근 제어 | 15 | published | 2025-03-16 |
6W - EKS api 서버 접근 보안 | 16 | published | 2025-03-16 |
7W - 쿠버네티스의 스케줄링, 커스텀 스케줄러 설정 | 17 | published | 2025-03-22 |
7W - EKS Fargate | 18 | published | 2025-03-22 |
7W - EKS Automode | 19 | published | 2025-03-22 |
8W - 아르고 워크플로우 | 20 | published | 2025-03-30 |
8W - 아르고 롤아웃 | 21 | published | 2025-03-30 |
8W - 아르고 CD | 22 | published | 2025-03-30 |
8W - CICD | 23 | published | 2025-03-30 |
9W - EKS 업그레이드 | 24 | published | 2025-04-02 |
10W - Vault를 활용한 CICD 보안 | 25 | published | 2025-04-16 |
11W - EKS에서 FSx, Inferentia 활용하기 | 26 | published | 2025-04-18 |
11주차 - EKS에서 FSx, Inferentia 활용하기 | 26 | published | 2025-05-11 |
12W - VPC Lattice 기반 gateway api | 27 | published | 2025-04-27 |
관련 문서
이름 | noteType | created |
---|---|---|
StatefulSet | knowledge | 2024-12-26 |
스토리지 | knowledge | 2025-01-10 |
PersistentVolume | knowledge | 2025-01-11 |
StorageClass | knowledge | 2025-01-12 |
AWS EBS CSI Driver | knowledge | 2025-02-18 |
kubestr | knowledge | 2025-02-19 |
AWS EFS CSI Driver | knowledge | 2025-02-20 |
볼륨 스냅샷 | knowledge | 2025-02-20 |
E-NFS 볼륨, 스토리지 클래스 설정 | topic/explain | 2024-10-17 |
E-바인딩과 하드 링크의 차이 | topic/explain | 2025-01-16 |
E-emptyDir 제한 | topic/explain | 2025-01-16 |
E-파드 마운팅 recursiveReadOnly | topic/explain | 2025-02-27 |
E-projected 볼륨 - 동적 업데이트, 중복 활용 | topic/explain | 2025-03-10 |
T-vagrant 쿠버 버전 업그레이드 | topic/temp | 2025-01-14 |
T-볼륨 마운팅 위에 마운팅하기 | topic/temp | 2025-01-16 |
T-마운트 전파 Bidirectioal | topic/temp | 2025-02-28 |
참고
https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/making-instance-stores-available-on-your-instances.html ↩︎
https://aws.amazon.com/ko/blogs/containers/eks-persistent-volumes-for-instance-store/ ↩︎
https://velog.io/@ironkey/Prometheus-Grafana로-Kubernetes-모니터링하기 ↩︎
https://cloudinit.readthedocs.io/en/stable/reference/examples.html ↩︎
https://awslabs.github.io/amazon-eks-ami/nodeadm/doc/playground/ ↩︎
https://github.com/awslabs/amazon-eks-ami/tree/main/nodeadm ↩︎